<!-- components/advanced-websocket.vue -->
<template>
  <view style="width: 100%;">
    <!-- 状态显示插槽 -->
    <slot :status="socketStatus" :lastError="lastError">
      <view class="ws-status" :class="statusClass">
        {{ statusText }}
      </view>
    </slot>
  </view>
</template>

<script>
/**
 * 高级WebSocket组件
 * 功能：
 * 1. 支持发送：16进制字符串、16进制数组、普通字符串
 * 2. 自动识别接收：16进制数据、普通字符串
 * 3. 自动重连机制
 * 4. 心跳保活
 * 5. 完整的状态管理
 */
export default {
  name: 'advanced-websocket',
  props: {
    // 连接地址
    url: {
      type: String,
      required: true
    },
    // 是否自动连接
    autoConnect: {
      type: Boolean,
      default: true
    },
    // 最大重连次数
    maxReconnectAttempts: {
      type: Number,
      default: 5
    },
    // 重连间隔(ms)
    reconnectInterval: {
      type: Number,
      default: 3000
    },
    // 是否显示默认状态UI
    showStatus: {
      type: Boolean,
      default: true
    },
    // 心跳配置
    heartbeat: {
      type: Object,
      default: null,
      validator: config => {
        if (!config) return true
        return config.message && config.interval
      }
    }
  },
  data() {
    return {
      socket: null,               // WebSocket实例
      socketStatus: 'closed',     // 状态：closed/connecting/connected/error
      reconnectAttempts: 0,       // 当前重连次数
      reconnectTimer: null,       // 重连定时器
      heartbeatTimer: null,       // 心跳定时器
      lastError: null,            // 最后错误信息
      messageListeners: [],       // 消息监听器
      isManualClose: false        // 是否手动关闭
    }
  },
  computed: {
    // 状态显示文本
    statusText() {
      const map = {
        closed: '4G连接',
        connecting: '连接中...',
        connected: '4G已连接',
        error: this.lastError?.message || '连接错误'
      }
      return map[this.socketStatus]
    },
    // 状态样式类
    statusClass() {
      return {
        'ws-status-error': this.socketStatus === 'error',
        'ws-status-connecting': this.socketStatus === 'connecting',
        'ws-status-connected': this.socketStatus === 'connected',
        'ws-status-connecting': this.socketStatus === 'closed',
      }
    }
  },
  watch: {
    // URL变化时重新连接
    url() {
      if (this.socketStatus === 'connected') {
        this.reconnect()
      }
    }
  },
  created() {
    // 自动连接
    if (this.autoConnect) {
      this.connect()
    }
  },
  beforeDestroy() {
    // 组件销毁时关闭连接
    this.disconnect()
  },
  methods: {
    /* ========== 公开方法 ========== */
    
    /**
     * 连接WebSocket
     * @return {Promise} 连接成功resolve，失败reject
     */
    connect() {
      return new Promise((resolve, reject) => {
        // 已连接直接返回
        if (this.socketStatus === 'connected') {
          resolve()
          return
        }
        
        // 清理旧连接
        this._cleanup()
        this.isManualClose = false
        this.socketStatus = 'connecting'
        this.$emit('status-change', this.socketStatus)
        
        // 创建WebSocket连接
        this.socket = uni.connectSocket({
          url: this.url,
          complete: () => {
            // 设置事件监听
            this._setupEventListeners(resolve, reject)
          }
        })
      })
    },
    
    /**
     * 断开WebSocket连接
     */
    disconnect() {
      this.isManualClose = true
      this._cleanup()
      this.socketStatus = 'closed'
      this.$emit('disconnected')
      this.$emit('status-change', this.socketStatus)
    },
    
    /**
     * 重新连接
     * @return {Promise}
     */
    reconnect() {
      return this.disconnect().then(() => this.connect())
    },
    
    /**
     * 发送数据
     * @param {string|Array} data - 要发送的数据
     * @param {string} [type='string'] - 数据类型: 'hex'|'bytes'|'string'
     * @return {Promise}
     */
    send(data, type = 'string') {
      return new Promise((resolve, reject) => {
        if (this.socketStatus !== 'connected') {
          const err = new Error('4G未连接')
          this._handleError(err)
          return reject(err)
        }
        
        try {
          // 根据类型转换数据
          let payload
          switch (type) {
            case 'hex':
              payload = this._hexStringToBuffer(data)
              break
            case 'bytes':
              payload = this._bytesToBuffer(data)
              break
            default: // string
              payload = this._stringToBuffer(data)
          }
          
          // 发送数据
          this.socket.send({
            data: payload,
            success: () => resolve(),
            fail: (err) => {
              this._handleError(err)
              reject(err)
            }
          })
        } catch (err) {
          this._handleError(err)
          reject(err)
        }
      })
    },
    
    /**
     * 发送16进制字符串
     * @param {string} hexStr - 如 "A1B2" 或 "A1 B2"
     * @return {Promise}
     */
    sendHex(hexStr) {
      return this.send(hexStr, 'hex')
    },
    
    /**
     * 发送字节数组
     * @param {Array} bytes - 如 [0xA1, 0xB2]
     * @return {Promise}
     */
    sendBytes(bytes) {
      return this.send(bytes, 'bytes')
    },
    
    /**
     * 添加消息监听器
     * @param {Function} callback - 回调函数
     */
    onMessage(callback) {
      if (typeof callback === 'function') {
        this.messageListeners.push(callback)
      }
    },
    
    /**
     * 移除消息监听器
     * @param {Function} callback - 要移除的回调
     */
    offMessage(callback) {
      this.messageListeners = this.messageListeners.filter(cb => cb !== callback)
    },
    
    /* ========== 内部方法 ========== */
    
    // 设置WebSocket事件监听
    _setupEventListeners(resolve, reject) {
      // 连接打开
      this.socket.onOpen(() => {
        this.socketStatus = 'connected'
        this.reconnectAttempts = 0
        this.lastError = null
        this._startHeartbeat()
        this.$emit('connected')
        this.$emit('status-change', this.socketStatus)
        resolve()
      })
      
      // 连接错误
      this.socket.onError(err => {
        this._handleError(err)
        reject(err)
      })
      
      // 连接关闭
      this.socket.onClose(() => {
        if (this.socketStatus !== 'error') {
          this.socketStatus = 'closed'
          this.$emit('status-change', this.socketStatus)
        }
        this.$emit('disconnected')
        
        // 非手动关闭时尝试重连
        if (!this.isManualClose) {
          this._startReconnect()
        }
      })
      
      // 接收消息
      this.socket.onMessage(res => {
        try {
          // 解析接收到的数据
          const message = this._parseMessage(res.data)
          this.$emit('message', message.data)
          
          // 调用所有监听器
          this.messageListeners.forEach(cb => cb(message))
        } catch (err) {
          this._handleError(err)
        }
      })
    },
    
    // 解析接收到的消息
    _parseMessage(data) {
      // 判断数据类型并返回结构化结果
      if (typeof data === 'string') {
        // 可能是普通字符串或16进制字符串
        if (this._isHexString(data)) {
          return {
            type: 'hex',
            data: data,
            buffer: this._hexStringToBuffer(data),
            bytes: Array.from(this._hexStringToBuffer(data))
          }
        } else {
          return {
            type: 'string',
            data: data,
            buffer: this._stringToBuffer(data)
          }
        }
      } else if (data instanceof ArrayBuffer) {
        // 二进制数据
        const bytes = Array.from(new Uint8Array(data))
        return {
          type: 'bytes',
          data: this._bufferToHex(data),
          buffer: data,
          bytes: bytes
        }
      }
      
      // 未知格式返回原始数据
      return {
        type: 'unknown',
        data: data
      }
    },
    
    // 判断是否是16进制字符串
    _isHexString(str) {
      const hexStr = str.replace(/\s/g, '')
      return /^[0-9a-fA-F]+$/.test(hexStr) && hexStr.length % 2 === 0
    },
    
    // 16进制字符串转ArrayBuffer
    _hexStringToBuffer(hexStr) {
      hexStr = hexStr.replace(/\s/g, '')
      
      if (!/^[0-9a-fA-F]+$/.test(hexStr)) {
        throw new Error('无效的16进制字符串')
      }
      
      if (hexStr.length % 2 !== 0) {
        throw new Error('16进制字符串长度必须为偶数')
      }
      
      const buffer = new ArrayBuffer(hexStr.length / 2)
      const view = new Uint8Array(buffer)
      
      for (let i = 0, j = 0; i < hexStr.length; i += 2, j++) {
        view[j] = parseInt(hexStr.substr(i, 2), 16)
      }
      
      return buffer
    },
    
    // 字节数组转ArrayBuffer
    _bytesToBuffer(bytes) {
      if (!Array.isArray(bytes)) {
        throw new Error('字节数组格式不正确')
      }
      
      const buffer = new ArrayBuffer(bytes.length)
      const view = new Uint8Array(buffer)
      
      for (let i = 0; i < bytes.length; i++) {
        const byte = bytes[i]
        if (byte < 0 || byte > 255) {
          throw new Error(`无效的字节值: ${byte}`)
        }
        view[i] = byte
      }
      
      return buffer
    },
    
    // 字符串转ArrayBuffer（兼容方案）
    // 在WebSocket组件中添加这个方法
    _stringToBuffer(str) {
      // 尝试使用TextEncoder（如果可用）
      if (typeof TextEncoder !== 'undefined') {
        try {
          const encoder = new TextEncoder()
          return encoder.encode(str).buffer
        } catch (e) {
          console.warn('TextEncoder failed, using fallback')
        }
      }
      
      // 兼容方案：支持基本非ASCII字符
      const charCodes = []
      for (let i = 0; i < str.length; i++) {
        const charCode = str.charCodeAt(i)
        // 处理UTF-16代理对（用于表情符号等）
        if (charCode > 0xd800 && charCode < 0xdbff && i + 1 < str.length) {
          const nextCharCode = str.charCodeAt(i + 1)
          if (nextCharCode > 0xdc00 && nextCharCode < 0xdfff) {
            // 计算代理对的码点
            const codePoint = (charCode - 0xd800) * 0x400 + (nextCharCode - 0xdc00) + 0x10000
            // UTF-8编码
            charCodes.push(0xf0 | (codePoint >> 18))
            charCodes.push(0x80 | ((codePoint >> 12) & 0x3f))
            charCodes.push(0x80 | ((codePoint >> 6) & 0x3f))
            charCodes.push(0x80 | (codePoint & 0x3f))
            i++ // 跳过下一个字符
            continue
          }
        }
        
        // 常规UTF-8编码
        if (charCode < 0x80) {
          charCodes.push(charCode)
        } else if (charCode < 0x800) {
          charCodes.push(0xc0 | (charCode >> 6))
          charCodes.push(0x80 | (charCode & 0x3f))
        } else {
          charCodes.push(0xe0 | (charCode >> 12))
          charCodes.push(0x80 | ((charCode >> 6) & 0x3f))
          charCodes.push(0x80 | (charCode & 0x3f))
        }
      }
      
      const buffer = new ArrayBuffer(charCodes.length)
      const view = new Uint8Array(buffer)
      charCodes.forEach((code, index) => {
        view[index] = code
      })
      return buffer
    },
    
    // ArrayBuffer转16进制字符串
    _bufferToHex(buffer) {
      const view = new Uint8Array(buffer)
      let hex = ''
      for (let i = 0; i < view.length; i++) {
        const byte = view[i].toString(16).padStart(2, '0')
        hex += byte + (i < view.length - 1 ? ' ' : '')
      }
      return hex.toUpperCase()
    },
    
    // 开始重连
    _startReconnect() {
      if (this.reconnectAttempts >= this.maxReconnectAttempts) {
        console.log(`[WebSocket] 达到最大重连次数 ${this.maxReconnectAttempts}`)
        return
      }
      
      clearTimeout(this.reconnectTimer)
      this.reconnectAttempts++
      
      console.log(`[WebSocket] 尝试第 ${this.reconnectAttempts} 次重连...`)
      this.reconnectTimer = setTimeout(() => {
        this.connect().catch(() => {
          this._startReconnect()
        })
      }, this.reconnectInterval)
    },
    
    // 开始心跳
    _startHeartbeat() {
      if (!this.heartbeat) return
      
      clearInterval(this.heartbeatTimer)
      
      const sendHeartbeat = () => {
		  console.log('发送心跳',this.heartbeat.message);
        if (this.socketStatus === 'connected') {
          this.send(
            this.heartbeat.message,
            this.heartbeat.type || 'string'
          ).catch(err => {
            console.error('[WebSocket] 心跳发送失败:', err)
            // 心跳失败时尝试重新连接
            this._startReconnect()
          })
        }
      }
      
      // 立即发送一次心跳
      sendHeartbeat()
      // 设置定时心跳
      this.heartbeatTimer = setInterval(
        sendHeartbeat, 
        this.heartbeat.interval
      )
    },
    
    // 错误处理
    _handleError(err) {
      console.error('[WebSocket] 错误:', err)
      this.socketStatus = 'error'
      this.lastError = err
      this.$emit('error', err)
      this.$emit('status-change', this.socketStatus)
      
      // 自动开始重连
      if (!this.isManualClose) {
        this._startReconnect()
      }
    },
    
    // 清理资源
    _cleanup() {
      clearTimeout(this.reconnectTimer)
      clearInterval(this.heartbeatTimer)
      
      if (this.socket) {
        this.socket.close()
        this.socket = null
      }
    }
  }
}
</script>

<style scoped>
.ws-status {
/*  display: inline-block;
  padding: 4rpx 12rpx;
  border-radius: 50rpx;
  font-size: 30rpx;
  background-color: #f0f0f0;
  color: #666; */
  /* width: 100%; */
  /* margin: 0 20rpx; */
  text-align: center;
  color: #ffffff;
  font-size: 30rpx;
  padding: 20rpx 0;
  border-radius: 200rpx;
}

.ws-status-connected {
  background-color: #21a675;
  color: white;
}

.ws-status-connecting {
  background-color: #ffc107;
  color: #000;
  /* animation: pulse 1.5s infinite; */
}

.ws-status-error {
  background-color: #dd524d;
  color: white;
}

@keyframes pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.6; }
}
</style>